﻿#region using statements

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using NGen;
using NGen.Managers.GUI;
using NGen.Managers.GameObject;
using NGen.Managers.GameState;
using NGen.Managers.Input;
using NGen.Systems.StringAlarms;

using Nuclex.Input;

using TowerDefense.GUILayouts;
using TowerDefense.Misc;
using TowerDefense.Objects.Enemies;
using TowerDefense.Objects.Towers;

#endregion

namespace TowerDefense.States
{
	/// <summary>
	/// 	State of the game whenever the play screen is displayed
	/// </summary>
	public class GameStatePlay : GameState, IMouseListener, IKeyboardListener, IAlarmListener
	{
		#region local fields

		/// <summary>
		/// 	alarm system used for timed events
		/// </summary>
		private readonly AlarmSystem m_alarms = new AlarmSystem();

		/// <summary>
		/// 	the gui buttons
		/// </summary>
		private readonly GUIElementPlayStateLayout m_gui = new GUIElementPlayStateLayout();

		private GameLevel m_level;

		/// <summary>
		/// 	a saved reference to the alarm used to delay levels
		/// </summary>
		private AlarmObject m_levelAlarm;

		/// <summary>
		/// 	the new tower being placed. null if no tower clicked
		/// </summary>
		private GameObjectTowerBase m_newTower;

		/// <summary>
		/// 	the currentlt select tower. null if no tower selected
		/// </summary>
		private GameObjectTowerBase m_selectedTower;

		private bool m_shiftHeld;

		#endregion

		#region local methods

		/// <summary>
		/// 	Called whenever a gui button gets pressed. This is setup in the
		/// 	initialize function
		/// </summary>
		/// <param name = "guiEvent">a string to represent the event</param>
		private void _GUIEvent(string guiEvent) {
			switch(guiEvent) {
				case "firePressed":
					if(GameEngine.GetService<GameStats>().Gold < 25) {
						return;
					}
					m_newTower = new GameObjectTowerFire();
					_SelectTower(m_newTower);
					break;
				case "icePressed":
					if(GameEngine.GetService<GameStats>().Gold < 25) {
						return;
					}
					m_newTower = new GameObjectTowerIce();
					_SelectTower(m_newTower);
					break;
				case "windPressed":
					if(GameEngine.GetService<GameStats>().Gold < 25) {
						return;
					}
					m_newTower = new GameObjectTowerWind();
					_SelectTower(m_newTower);
					break;
				case "startPressed":
					m_levelAlarm.TimeRemaning = 0;
					break;
				case "sellPressed":
					if(m_selectedTower == null) {
						return;
					}
					GameEngine.GetService<GameStats>().Gold += m_selectedTower.SellPrice;
					GameEngine.GetService<GameLevel>().LevelGrid[m_selectedTower.ArrayPosition.X, m_selectedTower.ArrayPosition.Y] = 0;
					GameObject.Destroy(m_selectedTower.Id);
					_DeselectTower();

					break;
				case "upgradePressed":
					if(m_selectedTower == null) {
						return;
					}
					m_selectedTower.UpgradeTowerLevel();
					_SelectTower(m_selectedTower);
					break;
			}
		}

		/// <summary>
		/// 	Attempts to select a tower. Checks if the mouse is currently 
		/// 	hovering over a tower, if so, that tower becomes selected
		/// </summary>
		private void _TrySelectTower() {
			//get mouse screen coordinates
			var mousePos = GameLevel.GetWorldCoords(new Vector2(GameEngine.GetService<IManagerInput>().GetMouseState().X, GameEngine.GetService<IManagerInput>().GetMouseState().Y));

			//check if they are outside of world bounds
			if(mousePos.X >= GameLevel.LevelWidth ||
			   mousePos.Y > GameLevel.LevelHeight) {
				return;
			}

			//deselect any currently selected tower
			_DeselectTower();

			//if there is a tower where we just clicked, select it
			if(m_level.LevelGrid[mousePos.X, mousePos.Y] > 0) {
				_SelectTower(GameEngine.GetService<IManagerGameObjects>().GetGameObjectById<GameObjectTowerBase>((uint) m_level.LevelGrid[mousePos.X, mousePos.Y]));
			}
		}

		/// <summary>
		/// 	Attempts to place a tower. Checks if a tower is already at the
		/// 	mouse location before placing.
		/// </summary>
		private void _TryPlaceNewTower() {
			m_newTower.ArrayPosition = GameLevel.GetWorldCoords(m_newTower.m_position);

			//ensure no tower exists at this location first
			if(m_level.LevelGrid[m_newTower.ArrayPosition.X, m_newTower.ArrayPosition.Y] != 0) {
				return;
			}

			//add tower to object manager
			GameEngine.GetService<IManagerGameObjects>().AddObject(m_newTower);

			//update the 2D array and put this tower in the array
			m_level.LevelGrid[m_newTower.ArrayPosition.X, m_newTower.ArrayPosition.Y] = (int) m_newTower.Id;

			_DeselectTower();

			GameEngine.GetService<GameStats>().Gold -= 25;
			if(m_shiftHeld && GameEngine.GetService<GameStats>().Gold >= 25) {
				//if holding shift, make another copy of this tower to place
				m_newTower = m_newTower.GetType().GetConstructors()[0].Invoke(null) as GameObjectTowerBase;
				m_newTower.m_position.X = -1000;
				_SelectTower(m_newTower);
				return;
			}
			//tower was placed, can no longer move it
			_SelectTower(m_newTower);
			m_newTower = null;
		}

		/// <summary>
		/// 	Select a specific tower
		/// </summary>
		/// <param name = "tower">the tower to be selected</param>
		private void _SelectTower(GameObjectTowerBase tower) {
			if(m_selectedTower != null) {
				_DeselectTower();
			}

			m_selectedTower = tower;
			m_selectedTower.Selected = true;
			m_gui.SelectedTower = tower;
		}

		/// <summary>
		/// 	Deselect the currently selected tower
		/// </summary>
		private void _DeselectTower() {
			if(m_selectedTower == null) {
				return;
			}

			m_selectedTower.Selected = false;
			m_selectedTower = null;
			m_gui.SelectedTower = null;
		}

		#endregion

		#region GameState implementation

		/// <summary>
		/// 	_Initialize this game state
		/// </summary>
		protected override void _Initialize() {
			//load the attack radius texture
			GameObjectTowerBase.m_attackRadiusTexture = GameEngine.GameContent.Load<Texture2D>("Content\\Textures\\Towers\\circle");

			//create the stats object and make it accessible as a service
			var stats = new GameStats {
				Lives = 20,
				Gold = 100,
				Level = 0
			};
			if(GameEngine.GetService<GameStats>() != null) {
				GameEngine.GameServices.RemoveService(typeof(GameStats));
			}
			GameEngine.GameServices.AddService(typeof(GameStats), stats);

			//provides a way for towers to access the list of enemies
			if(GameEngine.GetService<EnemyList>() != null) {
				GameEngine.GameServices.RemoveService(typeof(EnemyList));
			}
			GameEngine.GameServices.AddService(typeof(EnemyList), new EnemyList());

			//load the game world
			m_level = new GameLevel(GameEngine.GetService<GameOptions>().MapName);

			if(GameEngine.GetService<GameLevel>() != null) {
				GameEngine.GameServices.RemoveService(typeof(GameLevel));
			}
			GameEngine.GameServices.AddService(typeof(GameLevel), m_level);
			GameObjectEnemyBase.m_lWaypoint = m_level.GetEnemyPath();

			//load the gui
			GameEngine.GetService<IManagerGUI>().AddElement(m_gui);
			m_gui.GuiEvent += _GUIEvent; //listen for gui button presses

			//start the timer for the first wave
			m_levelAlarm = new AlarmObject(this, 5.0f, "nextLevel");
			m_alarms.Add(m_levelAlarm);
		}

		/// <summary>
		/// 	Perform all necessary actions to cleanup after this state exits
		/// </summary>
		protected override void _Cleanup() {
			GameEngine.GetService<IManagerGUI>().RemoveElement(m_gui);
			GameEngine.GetService<IManagerInput>().RemoveMouseListener(this);
			GameEngine.GetService<IManagerInput>().RemoveKeyboardListener(this);
			GameEngine.GetService<IManagerGameObjects>().DestroyAllGameObjects();
		}

		/// <summary>
		/// 	Called whenever this state is no longer the active state
		/// </summary>
		protected override void _OnFocusLost() {
			//this state shouldn't receive input while its not active
			GameEngine.GetService<IManagerInput>().RemoveKeyboardListener(this);
			GameEngine.GetService<IManagerInput>().RemoveMouseListener(this);
			GameEngine.GetService<IManagerGameObjects>().SkipUpdate = true;
		}

		/// <summary>
		/// 	Called whenever this state becomes the active state
		/// </summary>
		protected override void _OnFocusGained() {
			//this state should receive input again
			GameEngine.GetService<IManagerInput>().AddKeyboardListener(this);
			GameEngine.GetService<IManagerInput>().AddMouseListener(this);
			GameEngine.GetService<IManagerGameObjects>().SkipUpdate = false;
		}

		/// <summary>
		/// 	Update the game for this frame
		/// </summary>
		/// <param name = "p_gameTime">snapshot of timing values</param>
		protected override void _Update(GameTime p_gameTime) {
			m_alarms.Update(p_gameTime);
			m_gui.SetNextLevelTimer = ((int) m_levelAlarm.TimeRemaning).ToString();
			if(GameEngine.GetService<GameStats>().Lives <= 0) {
				GameEngine.GetService<IManagerGameState>().ChangeState(new GameStateGameOver());
			}
		}

		/// <summary>
		/// 	_Draw the game for this frame
		/// </summary>
		/// <param name = "p_gameTime">snapshot of timing values</param>
		protected override void _Draw(GameTime p_gameTime) {
			//clear the backbuffer
			GameEngine.GraphicsDeviceManager.GraphicsDevice.Clear(Color.Transparent);

			//initialize the backbuffer
			GameEngine.SpriteBatch.Begin();

			

			GameEngine.SpriteBatch.Draw( //draw the background
				m_level.LevelImage, new Vector2(12, 12), null, Color.White, 0.0f, Vector2.Zero, Vector2.One, SpriteEffects.None, 0.01f);

			if(m_newTower != null) {
				m_newTower.WhilePlacing();
			}

			//render to backbuffer
			GameEngine.SpriteBatch.End();
		}

		#endregion

		#region IMouseListener implementation

		/// <summary>
		/// 	Called whenever the mouse moves
		/// </summary>
		/// <param name = "x">x value of mouse position</param>
		/// <param name = "y">y value of mouse position</param>
		public void MouseMoved(float x, float y) {
			//if there is a new tower ready to be placed, have the tower follow
			// the mouse around
			if(m_newTower == null) {
				return;
			}

			//if mouse is outside map bounds, move tower offscreen
			if(x >= 588 ||
			   x < 12) {
				x = -1000;
			}
			if(y >= 588 ||
			   y < 12) {
				y = -1000;
			}

			m_newTower.m_position.X = x - GameLevel.MapOffset.X;
			m_newTower.m_position.Y = y - GameLevel.MapOffset.Y;
			GameLevel.SnapToGrid(ref m_newTower.m_position);
		}

		/// <summary>
		/// 	Called whenever a mouse button is pressed
		/// </summary>
		/// <param name = "button">the button pressed</param>
		public void MousePressed(MouseButtons button) {
			switch(button) {
				case MouseButtons.Left: {
					//if we are not placing a new tower
					if(m_newTower == null) {
						_TrySelectTower();
					} else if( //else if the new tower is located in world
						m_newTower.m_position.X > 11 &&
						m_newTower.m_position.Y > 11) {
						_TryPlaceNewTower();
					}
				}
					break;
				case MouseButtons.Right:
					m_newTower = null;
					break;
			}
		}

		/// <summary>
		/// 	Called whenever a mouse button is released
		/// </summary>
		/// <param name = "button">the button released</param>
		public void MouseReleased(MouseButtons button) {}

		/// <summary>
		/// 	Called whenever the mouse wheel is scrolled
		/// </summary>
		/// <param name = "ticks">the number of ticks scrolled</param>
		public void MouseWheelScroll(float ticks) {}

		#endregion

		#region IKeyboardListener implementation

		/// <summary>
		/// 	Called whenever a keyboard button is pressed
		/// </summary>
		/// <param name = "key">the key pressed</param>
		public void KeyPressed(Keys key) {
			switch(key) {
				case (Keys) 16:
					m_shiftHeld = true;
					break;
				case Keys.U:
					if(m_newTower == null) {
						_GUIEvent("upgradePressed");
					}
					break;
				case Keys.S:
					if(m_newTower == null) {
						_GUIEvent("sellPressed");
					}
					break;
				case Keys.Escape:
					var state = new GameStatePause();
					//this is just to have the pause state call the play state draw
					state.ThrowDrawEvent += _Draw;
					GameEngine.GetService<IManagerGameState>().PushState(state);
					break;
			}
		}

		/// <summary>
		/// 	Called whenever a keyboard button is released
		/// </summary>
		/// <param name = "key">the key released</param>
		public void KeyReleased(Keys key) {
			if(key == (Keys) 16) {
				m_shiftHeld = false;
			}
		}

		#endregion

		#region IAlarmListener implementation

		/// <summary>
		/// 	Called when an alarm event happens
		/// </summary>
		/// <param name = "message">the alarm message</param>
		public void OnAlarmEvent(string message) {
			switch(message) {
				case "nextLevel":
					//increment the level
					GameEngine.GetService<GameStats>().Level += 1;

					//create the enemy spawner for this level
					var newSpawner = new GameObjectEnemySpawner {
						SkipDraw = true
					};

					//put the spawner in the objects manager
					GameEngine.GetService<IManagerGameObjects>().AddObject(newSpawner);

					//load the spawner with enemies
					for(var i = 0; i < 10; i++) {
						newSpawner.AddEnemy(new GameObjectEnemy());
					}

					//set the alarm for the next level to start in 20 seconds
					m_levelAlarm = new AlarmObject(this, 20.0f, "nextLevel");

					//add alarm to alarm system
					m_alarms.Add(m_levelAlarm);
					break;
			}
		}

		#endregion
	}
}